SpringCloudAlibaba 入门笔记(四)Sentinel 服务限流降级

概述

Sentinel是一个以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务稳定性的分布式系统流量防控兵。

Sentinel 具有以下特征:

  • 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
  • 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
  • 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
  • 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

注:以上描述摘自 Sentinel官方文档

在本篇中,我们将使用Sentinel对服务进行流量控制、熔断降级以及基于nacos的规则持久化

创建项目

首先,我们来创建一个 SpringBoot 项目,就起名sentinel-test吧。

然后,引入如下依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>

流量控制

项目创建完成后,让我们来测试一下Sentinel的流量控制。在Sentinel中,流量控制是通过抛出BlockException来实现的,我们可以通过@SentinelResource来标记资源和处理异常的方法,共有两种方式:

  • 在单独类中处理异常:使用blockHandlerClass来指定处理异常的类,使用blockHandler指定类中处理异常的方法,这个方法必须是一个静态方法,且要求该静态方法的参数列表在被标记的资源基础上,添加一个BlockException,参考后续代码中的/test1
  • 在本类中处理异常: 使用blockHandler指定本类中的一个处理异常的方法,要求该静态方法的参数列表在被标记的资源基础上,添加一个BlockException,参考后续代码中的/test2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@RestController
public class TestController {

/**
* test1
* 流量控制测试
* <p>
* 将对BlockException的处理放入一个单独的ExceptionUtil类中,这种方式要求处理异常的handleTest1方法为静态方法
* 且要求handleTest1方法的参数列表在被标记资源的基础上,添加一个BlockException
*/
@GetMapping(value = "/test1")
@SentinelResource(value = "test1", blockHandler = "handleTest1", blockHandlerClass = ExceptionUtil.class)
public String test1() {
return "请求资源 : test1";
}

/**
* test2
* 流量控制测试
* <p>
* 将对BlockException的处理放在当前类中
*
*/
@GetMapping(value = "/test2")
@SentinelResource(value = "test2", blockHandler = "handleTest2")
public String test2() {
return "请求资源 : test2";
}

/**
* BlockException处理函数,参数列表最后多一个 BlockException,其余与原方法一致
*/
public String handleTest2(BlockException ex) {
// 进行一些日志处理
ex.printStackTrace();
return "流量控制 : test2";
}

}

下面,我们来设定具体的流量控制规则,流控规则也是以资源为切入点的,我们可以通过代码来配置,也可以通过外Sentinel控制台来配置。

方式一:代码配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootApplication
public class SentinelTestApplication {

public static void main(String[] args) {
SpringApplication.run(SentinelTestApplication.class);
initRules();
}

/**
* 硬编码定义规则
*/
private static void initRules() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule = new FlowRule();
rule.setResource("test1"); // 资源名
rule.setGrade(RuleConstant.FLOW_GRADE_QPS); // 流控方式,这里通过qps来进行控制
rule.setCount(1); // 阈值,当qps超过1时进行流量控制
rules.add(rule);
rule = new FlowRule();
rule.setResource("test2");
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule.setCount(1);
rules.add(rule);
FlowRuleManager.loadRules(rules);
}
}

OK,下面我们启动项目进行测试,让我们疯狂访问/test1和/test2接口,会看到类似下面的提示:

请求资源 : test1
流量控制 : test1
流量控制 : test1
请求资源 : test1
流量控制 : test1
流量控制 : test1

表明流量控制起到了作用,如果我们将流控的阈值设置的比较大,手动刷新时就基本不会出现流量控制的情况了,因为我们的手速并没有那么快。

当然,流控的规则并不是只有QPS一种,文末会给出一些配置项的具体说明,在这里就不细说了。

熔断降级

除了流量控制,Sentinel还提供了熔断降级机制,比如当一个接口频繁出现错误时,Sentinel就会按照配置的降级规则对接口进行降级处理。

Sentinel的熔断降级是通过DegradeException来实现的,下面我们来模拟一下这种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@RestController
public class TestController {

/**
* test3
* 测试熔断降级
*/
@GetMapping(value = "/test3")
@SentinelResource(value = "test3", fallback = "handleDegrade")
public String test3() {
if (new Random().nextBoolean()) {
int i = 1 / 0; // 模拟一个业务异常
}
return "请求资源 : test3";
}

/**
* 熔断降级处理函数
*/
public String handleDegrade(Throwable throwable) {
if (throwable instanceof DegradeException) {
return "服务已被降级 = " + new Date();
} else {
return "业务出现异常 = " + new Date();
}
}

}

在上面的流量控制中,我们采用了硬编码的方式来定义流控规则,在本小节,我们通过Sentinel Dashboard来配置降级规则。

  1. 我们需要去下载 Sentinel DashBoard,这是一个基于SpringBoot编写的程序,打包成jar包供我们使用。

  2. 通过命令 java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar启动,端口号可以根据需要去进行修改,我这里改为8899

  3. 访问localhost:8899就可以看到Sentinel Dashboard了

接下来,我们修改一下项目的配置,在application.yml中进行如下配置:

1
2
3
4
5
6
7
8
spring:
application:
name: sentinel-test
cloud:
sentinel:
transport:
port: 8103
dashboard: localhost:8899

请注意,我们这里配置了一个8103的端口号,sentinel会自动在这个端口启动一个Http服务,并通过这个服务与dashboard进行通信,这样一来,我们在dashboard上添加的降级规则就会被推送到我们的项目中。

在dashboard中,我们对资源test3新增一条降级规则,这里采用异常比例规则,取值0.1表示当每秒钟出现异常的比例达到十分之一,就对资源降级10秒钟。


这里有一个特别注意的点,上图的降级规则中有一项是异常数,这个表示的是1分钟内的异常数,如果我们使用这个规则,且降级时间设定的小于一分钟,那么就有可能出现资源已经超过降级时间窗口,但依然是被降级的状态。

下面我们启动项目进行测试,让我们疯狂访问/test3,会看到类似下面的提示:

请求资源 : test3
业务出现异常 = Thu Jun 04 10:37:51 CST 2020
服务已被降级 = Thu Jun 04 10:37:52 CST 2020
服务已被降级 = Thu Jun 04 10:37:53 CST 2020
服务已被降级 = Thu Jun 04 10:37:54 CST 2020
服务已被降级 = Thu Jun 04 10:37:55 CST 2020

表明熔断降级起到了作用。

规则持久化

虽然通过Sentinel Dashboard来设置规则可以避免硬编码的方式,但Dashboard中的规则是基于内存来存储的,重启后就会消失,而我们希望通过持久化的方式来存储规则。我们就可以借助Nacos配置中心来实现这个目标,关于Nacos的配置和启动这里就不再赘述了。

首先,添加依赖

1
2
3
4
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

然后修改配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
spring:
application:
name: sentinel-test
cloud:
sentinel:
transport:
port: 8103
dashboard: localhost:8899
datasource: # 持久化sentinel规则
degrade: # 熔断降级规则数据源配置
nacos:
server-addr: 127.0.0.1:8848
data-id: sentinel-test-degrade
group-id: SENTINEL_RULES
data-type: json
rule-type: degrade # flow,degrade,authority,system, param-flow
flow: # 流控规则数据源配置
nacos:
server-addr: 127.0.0.1:8848
data-id: sentinel-test-flow
group-id: SENTINEL_RULES
data-type: json
rule-type: flow

在Nacos控制台中创建如下所示的配置:

注意这两个配置都是json格式,内容如下:

sentinel-test-flow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[
{
"resource": "test1",
"limitApp": "default",
"grade": 1,
"count": 2,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
},
{
"resource": "test2",
"limitApp": "default",
"grade": 1,
"count": 2,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]

sentinel-test-degrade

1
2
3
4
5
6
7
8
[
{
"resource": "test3",
"count": 0.1,
"timeWindow": 10,
"grade": 1
}
]

ok,启动项目后,再次进行测试,我们就会得到和之前测试相同的结果了。

到这里,关于Sentinel的流控和熔断降级就实验完成了,其实Sentinel提供给我们的功能还远不止这些,其他的热点参数限流、集群限流、网关限流等也都是很实用的流控策略,相信通过本篇的流控和熔断降级的实验,其他策略的使用也不在话下了。

最后,对一些配置参数进行简单的说明

FlowRule(流控规则)

字段 描述 默认值
resource 资源名
limitApp 针对的调用来源,若为 default 则不区分调用来源 default
grade 限流阈值类型(QPS或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制 QPS模式
count 限流阈值
strategy 调用关系限流策略(0表示调用方直接限流、1表示关联资源限流、2表示链路限流)
controlBehavior 调用关系限流策略(0表示调用方直接限流、1表示关联资源限流、2表示链路限流) 拒绝
clusterMode 是否为集群模式 clusterMode

DegradeRule(熔断降级规则)

字段 描述 默认值
resource 资源名
limitApp 针对的调用来源,若为 default 则不区分调用来源 default
count 阈值
grade 降级模式,根据RT(0) 、每秒异常比例(1)、60s内异常数(2) RT(0)
timeWindow 降级的时间,单位为秒

源码地址:https://github.com/GreedyStar/spring-cloud-alibaba-demo

最后的最后,安利一下自己写的一个Java代码生成工具,能够方便的生成Spring、SpringMVC、Mybatis架构下的Java代码,希望能对大家有所帮助,地址:Java代码生成器:Generator